package ic.types;

import common.exceptions.SemanticError;
import ic.ast.decl.*;

import java.util.*;

/**
 * Created with IntelliJ IDEA.
 * User: Roi
 * Date: 12/5/13
 */
public class TypeTable {

    private static Map<String, ClassType> classTypeMap;
    private static Map<String, DeclClass> declClassMap;
    private static Map<String, Type> arrayTypeMap;
    private static Map<String, Type> primitiveTypeMap;
    private static Map<String, MethodType> methodTypeMap;
    private static int counter;

    /**
     * Static block - initialize all data
     */
    static {
        initialize();
    }

    public static void initialize() {
        // Initialize data maps
        classTypeMap = new LinkedHashMap<String, ClassType>();
        declClassMap = new LinkedHashMap<String, DeclClass>();
        arrayTypeMap = new LinkedHashMap<String, Type>();
        primitiveTypeMap = new LinkedHashMap<String, Type>();
        methodTypeMap = new LinkedHashMap<String, MethodType>();

        // Initialize counter
        counter = 1;

        // Initialize primitive types in the table by the requested order
        Type intType = new PrimitiveType(0, PrimitiveType.DataType.INT);
        intType.setTypeTableID(counter++);
        primitiveTypeMap.put("int", intType);
        Type boolType = new PrimitiveType(0, PrimitiveType.DataType.BOOLEAN);
        boolType.setTypeTableID(counter++);
        primitiveTypeMap.put("boolean", boolType);
        Type nullType = new PrimitiveType(0, PrimitiveType.DataType.VOID);
        nullType.setTypeTableID(counter++);
        primitiveTypeMap.put("null", nullType);
        Type stringType = new PrimitiveType(0, PrimitiveType.DataType.STRING);
        stringType.setTypeTableID(counter++);
        primitiveTypeMap.put("string", stringType);
        Type voidType = new PrimitiveType(0, PrimitiveType.DataType.VOID);
        voidType.setTypeTableID(counter++);
        primitiveTypeMap.put("void", voidType);
    }

    public static DeclClass getClassTypeByName(String className) throws SemanticError {
        DeclClass declClass = declClassMap.get(className);
        if (declClass == null) {
            throw new SemanticError("Class: " + className + " does not exist");
        }

        return declClass;
    }

    public static void addDeclClass(DeclClass declClass) throws SemanticError {
        if (declClassMap.containsKey(declClass.getName())) {
            throw new SemanticError(declClass.getLine(), "A class with the name " + declClass.getName() + " already exists");
        }

        if (declClass.hasSuperClass()) {
            if (declClass.getName().equals(declClass.getSuperClassName())) {
                throw new SemanticError(declClass.getLine(), "Class " + declClass.getName()
                        + " cannot extend " + declClass.getSuperClassName()
                        + ", since it's not yet defined");
            }
            if (!declClassMap.containsKey(declClass.getSuperClassName())) {
                throw new SemanticError(declClass.getLine(), "Class " + declClass.getName()
                        + " cannot extend " + declClass.getSuperClassName()
                        + ", since it's not yet defined");
            }
        }

        declClass.setTypeTableID(TypeTable.counter);
        TypeTable.counter++;

        declClassMap.put(declClass.getName(), declClass);
        classTypeMap.put(declClass.getName(), new ClassType(declClass.getLine(), declClass.getName()));
    }

    public static Type getType(Type type) throws Exception {
        return getType(type, true);
    }

    public static Type getType(Type type, boolean isAdd) throws Exception {
        if (type.getArrayDimension() > 0) {
            String displayName = type.getDisplayName();
            int dimension = type.getArrayDimension();

            for (int i = 1; i <= dimension; i++) {
                displayName += "[]";
                if (!arrayTypeMap.containsKey(displayName)) {
                    if (isAdd) {
                        Type newType = type.clone(i);
                        newType.setTypeTableID(counter);
                        arrayTypeMap.put(displayName, newType);
                        TypeTable.counter++;
                    } else {
                        throw new SemanticError(type.getLine(), "Type: " + displayName + "does not exist");
                    }
                }
            }

            return arrayTypeMap.get(displayName);
        } else {
            if (primitiveTypeMap.containsKey(type.toString())) {
                return primitiveTypeMap.get(type.toString());
            } else if (classTypeMap.containsKey(type.toString())) {
                return classTypeMap.get(type.toString());
            } else {
                throw new SemanticError(type.getLine(), "Type: " + type + "does not exist");
            }
        }
    }

    public static MethodType getType(DeclMethod declMethod, boolean isAdd) throws Exception {
        MethodType methodType = new MethodType(declMethod);

        if (methodTypeMap.containsKey(methodType.toString())) {
            return methodTypeMap.get(methodType.toString());
        }

        if (isAdd) {
            methodTypeMap.put(methodType.toString(), methodType);
            TypeTable.counter++;
            return methodType;
        } else {
            throw new SemanticError(declMethod.getLine(), "Type: " + declMethod.getName() + "does not exist");
        }
    }

    public static MethodType getType(DeclMethod declMethod) throws Exception {
        return getType(declMethod, true);
    }

    public static Type getType(String name) {
        if (primitiveTypeMap.containsKey(name))
            return primitiveTypeMap.get(name);

        if (arrayTypeMap.containsKey(name))
            return arrayTypeMap.get(name);

        if (classTypeMap.containsKey(name))
            return classTypeMap.get(name);

        return null;
    }
}

